﻿//////////////////////////////////////////////////////////////////////////////
//
//  Copyright © 1998-2024 Glodon Company Limited.  All rights reserved.
//
//  Use of this software is subject to the terms of the Glodon license
//  agreement provided at the time of installation or download, or which
//  otherwise accompanies this software in either electronic or hard copy form.  
//
//////////////////////////////////////////////////////////////////////////////


#pragma once
#include "GnufBase.h"

namespace gnuf {

    struct UserDataUtils
    {
        template <typename TValue>
        static bool convertUserDataToValue(const gcmp::Any &, TValue &) noexcept;

        template <typename TPtr>
        static bool convertUserDataToPtr(const gcmp::Any &, TPtr &) noexcept;
    };
    
    class GNUF_BASE_EXPORT WithUserData
    {
    protected:
        explicit WithUserData();
        ~WithUserData();
        
    public:
        /// \brief Set the user data.
        inline void setUserData(const gcmp::Any &);
        /// \brief Reset the user data.
        inline void resetUserData();
        /// \return Return the user data.
        inline const gcmp::Any & userData() const { return m_userData; }
        
        /// \brief Convert the user data to a std::string value.
        /// \return Return false if empty or failed.
        bool userDataToStdString(std::string &) const;
        
        /// \brief Convert the user data to a std::wstring value.
        /// \return Return false if empty or failed.
        bool userDataToStdWString(std::wstring &) const;
        
        /// \brief Convert the user data to a bool value.
        /// \return Return false if failed.
        bool userDataToBool(bool &) const;
        
        /// \brief Convert the user data to an integer value.
        /// \return Return false if empty or failed.
        bool userDataToInt(int &) const;

        /// \brief Convert the user data to a float value.
        /// \return Return false if empty or failed.
        bool userDataToFloat(float &) const;
        
        /// \brief Convert the user data to a type of value.
        /// \return Return false if empty or failed.
        template <typename TValue>
        bool userDataToValue(TValue &) const noexcept;

        /// \brief Convert the user data to a type of pointer.
        /// \return Return false if empty or failed.
        /// \attention Cannot ensure whether the pointer is valid.
        template <typename TPtr>
        bool userDataToPtr(TPtr &) const noexcept;
        
    private:
        gcmp::Any m_userData;
    };

    class GNUF_BASE_EXPORT WithUserDataMap
    {
    public:
        explicit WithUserDataMap();
        ~WithUserDataMap();

        typedef int Key;
        typedef std::unordered_map< WithUserDataMap::Key, gcmp::Any > MapKeyToUserData;

        /// \brief Set the user data of the key. 
        /// If the key exists, modify the corresponding element. If doesn't exist, add an element.
        void setUserDataAt(const WithUserDataMap::Key, const gcmp::Any &);
        /// \brief Reset the user data.
        void resetUserDataAt(const WithUserDataMap::Key);
        /// \return Return true if there is the user data at the key.
        bool hasUserDataAt(const WithUserDataMap::Key) const;
        /// \return Return the user data.
        const gcmp::Any & userDataAt(const WithUserDataMap::Key) const;
        
        /// \return Return the number of the user data.
        size_t numberOfUserData() const { return m_userDataMap.size(); }
        /// \brief clear the user data map.
        void clearUserDataMap();
        
        /// \brief Convert the user data to a std::string value.
        /// \return Return false if empty or failed.
        bool userDataToStdStringAt(const WithUserDataMap::Key, std::string &) const;

        /// \brief Convert the user data to a std::wstring value.
        /// \return Return false if empty or failed.
        bool userDataToStdWStringAt(const WithUserDataMap::Key, std::wstring &) const;

        /// \brief Convert the user data to a bool value.
        /// \return Return false if failed.
        bool userDataToBoolAt(const WithUserDataMap::Key, bool &) const;

        /// \brief Convert the user data to an integer value.
        /// \return Return false if empty or failed.
        bool userDataToIntAt(const WithUserDataMap::Key, int &) const;

        /// \brief Convert the user data to a float value.
        /// \return Return false if empty or failed.
        bool userDataToFloatAt(const WithUserDataMap::Key, float &) const;
        
        /// \brief Convert the user data to a type of value.
        /// \return Return false if empty or failed.
        template <typename TValue>
        bool userDataToValueAt(const WithUserDataMap::Key key, TValue & value) const noexcept;

        /// \brief Convert the user data to a type of pointer.
        /// \return Return false if empty or failed.
        /// \attention Cannot ensure whether the pointer is valid.
        template <typename TPtr>
        bool userDataToPtrAt(const WithUserDataMap::Key key, TPtr & ptr) const noexcept;
    
    private:
        WithUserDataMap::MapKeyToUserData m_userDataMap;
    };
    
}

//_____________________________________________________________________________
// UserDataUtils
//
template <typename TValue>
bool gnuf::UserDataUtils::convertUserDataToValue(const gcmp::Any & ud, TValue & val) noexcept
{
    static_assert(!std::is_pointer< TValue >::value,
        "UserDataUtils::userDataToValue template type must not be a pointer type!");

    if (ud.empty())
    {
        return false;
    }
    else
    {
        try
        {
            val = ud.cast<TValue>();
        }
        catch (const gcmp::anyimpl::bad_any_cast &)
        {
            FYI(L"Failed to convert the user data to a type of value!");
            return false;
        }
    }
    
    return true;
}

template <typename TPtr>
bool gnuf::UserDataUtils::convertUserDataToPtr(const gcmp::Any & ud, TPtr & ptr) noexcept
{
    static_assert(std::is_pointer< TPtr >::value,
        "UserDataUtils::userDataToPtr template type must be a pointer type!");

    if (ud.empty())
    {
        return false;
    }
    else
    {
        try
        {
            ptr = ud.cast<TPtr>();
        }
        catch (const gcmp::anyimpl::bad_any_cast &)
        {
            FYI(L"Failed to convert the user data to a type of pointer!");
            return false;
        }
    }

    return true;
}

//_____________________________________________________________________________
// WithUserData
//
template <typename TValue>
bool gnuf::WithUserData::userDataToValue(TValue & val) const noexcept
{
    return gnuf::UserDataUtils::convertUserDataToValue< TValue >( m_userData, val );
}

template <typename TPtr>
bool gnuf::WithUserData::userDataToPtr(TPtr & ptr) const noexcept
{
    return gnuf::UserDataUtils::convertUserDataToPtr< TPtr >( m_userData, ptr );
}

//_____________________________________________________________________________
// WithUserDataMap
//
template <typename TValue>
bool gnuf::WithUserDataMap::userDataToValueAt(const WithUserDataMap::Key key, TValue & val) const noexcept
{
    auto found = m_userDataMap.find(key);
    if (m_userDataMap.end() == found)
        return false; // not found

    return gnuf::UserDataUtils::convertUserDataToValue< TValue >( (*found).second, val );
}

template <typename TPtr>
bool gnuf::WithUserDataMap::userDataToPtrAt(const WithUserDataMap::Key key, TPtr & ptr) const noexcept
{
    auto found = m_userDataMap.find(key);
    if (m_userDataMap.end() == found)
        return false; // not found

    return gnuf::UserDataUtils::convertUserDataToPtr< TPtr >( (*found).second, ptr );
}